package top.laoshuzi.android.mvp.compiler.processor;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;

import top.laoshuzi.android.mvp.facade.annotation.BindPresenter;

public class BindPresenterProcessor implements IProcessor {

    private Elements elementUtils;
    private Filer filer;

    public BindPresenterProcessor(ProcessingEnvironment processingEnv) {
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
    }

    @Override
    public void process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        Map<Element, List<Element>> viewElementMap = getElementMap(roundEnv, BindPresenter.class);

        for (Map.Entry<Element, List<Element>> entrySet : viewElementMap.entrySet()) {
            Element viewElement = entrySet.getKey();
            List<Element> bindPresenterElements = entrySet.getValue();


            String viewClassNameStr = viewElement.getSimpleName().toString();
            ClassName viewClassName = ClassName.bestGuess(viewClassNameStr);

            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(viewClassNameStr + "_MVPPresenterBinding")
                    .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
                    .addSuperinterface(ClassName.bestGuess("top.laoshuzi.android.mvp.view.MVPBinder"))
                    .addField(viewClassName, "mvp_view", Modifier.PRIVATE);

            MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
                    .addParameter(viewClassName, "view")
                    .addModifiers(Modifier.PUBLIC)
                    .addStatement("this.mvp_view = view");

            MethodSpec.Builder onUnbindMethodBuilder = MethodSpec.methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC);

            for (Element bindPresenterElement : bindPresenterElements) {
                String fieldName = bindPresenterElement.getSimpleName().toString();
                ClassName presenterClassName = ClassName.bestGuess("top.laoshuzi.android.mvp.presenter.Presenter");
                constructorMethodBuilder
                        .beginControlFlow("if($T.class.isAssignableFrom(mvp_view.$L.getClass()))", presenterClassName, fieldName)
                        .addStatement(" (($T) mvp_view.$L).create(mvp_view);", presenterClassName, fieldName)
                        .endControlFlow();
                onUnbindMethodBuilder
                        .beginControlFlow("if($T.class.isAssignableFrom(mvp_view.$L.getClass()))", presenterClassName, fieldName)
                        .addStatement(" (($T) mvp_view.$L).destroy();", presenterClassName, fieldName)
                        .endControlFlow();

            }


            String packageName = "top.laoshuzi.mvp";
//            String packageName = elementUtils.getPackageOf(viewElement).getQualifiedName().toString();
            classBuilder.addMethod(constructorMethodBuilder.build())
                    .addMethod(onUnbindMethodBuilder.build());
            try {
                JavaFile.builder(packageName, classBuilder.build())
                        .addFileComment("Automatically generated MVP code, do not modify!!!")
                        .build()
                        .writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private Map<Element, List<Element>> getElementMap(RoundEnvironment roundEnv, Class<? extends Annotation> a) {
        Map<Element, List<Element>> elementMap = new LinkedHashMap<>();

        Set<? extends Element> outsideElements = roundEnv.getElementsAnnotatedWith(a);
        for (Element element : outsideElements) {
            Element outsideElement = element.getEnclosingElement();
            List<Element> insideElements = elementMap.get(outsideElement);
            if (insideElements == null) {
                insideElements = new ArrayList<>();
                elementMap.put(outsideElement, insideElements);
            }
            insideElements.add(element);
        }
        return elementMap;
    }


}
